深入解析 JavaScript 的 this
在 JavaScript 的世界裡,this 關鍵字可以說是最令人困惑、也最常被誤解的概念之一。它不像其他程式語言中的 this 或 self 那樣運作。要真正掌握 JavaScript,理解 this 的運作方式至關重要。
this 的核心概念
this 的值並非在函式定義時就固定,而是在函式被呼叫時才決定的。這意味著同一個函式,根據它被呼叫的方式不同,其內部的 this 可能會指向完全不同的物件。
幾個關鍵原則:
this的值取決於函式如何被呼叫。一旦函式開始執行,其內部的
this就無法被改變。可以透過
.call(),.apply(), 和.bind()方法來手動控制this的值。
this 的繫結規則
要判斷 this 到底指向誰,我們可以依循以下幾種常見的繫結規則。
1. 全域環境 (Global Context)
當函式在全域範疇下直接被呼叫時,this 會指向全域物件。在瀏覽器環境中,這個全域物件就是 window。
var myFunction = function(){
console.log(this); // 在瀏覽器中會印出 Window 物件
console.log(this === window); // true
};
myFunction();
在嚴格模式 (Strict Mode) 下: 如果啟用嚴格模式 ('use strict'),在全域環境下呼叫函式,this 將會是 undefined,而不是 window 物件。這有助於避免意外污染全域物件。
'use strict'
var myFunction = function(){
console.log(this); // undefined
console.log(this === window); // false
};
myFunction();
巢狀函式呢? 即使是在一個函式內部再定義並呼叫另一個函式,只要內層函式是直接被呼叫,它的 this 仍然會遵循全域環境的規則。
function A() {
function B() {
// B() 是直接被呼叫,並非透過物件
console.log(this === window); // true
}
B();
}
A();
2. 隱含的繫結 (Implicit Binding)
當函式作為一個物件的方法被呼叫時,this 會被設定為呼叫該方法的物件。這也是最常見的 this 用法。
var animal = {
weight: 100,
walk: function () {
// walk() 是透過 animal 物件呼叫的
console.log(this.weight); // 100
console.log(this === animal); // true
}
};
animal.walk();
失去隱含的繫結 這是一個常見的陷阱。如果我們將物件的方法指派給另一個變數,然後再透過這個新變數來呼叫函式,那麼「隱含的繫結」就會消失,this 會退回全域環境的規則。
function run() {
console.log(this.model);
}
var car = {
model: 'nissan',
run: run
};
var alt = car.run; // 將方法指派給新變數 alt
alt(); // undefined (因為 this 指向 window,而 window.model 不存在)
在上面的例子中,alt() 執行時,它已經與 car 物件脫鉤,變成一個單純的全域函式呼叫。
3. 明確的繫結 (Explicit Binding)
有時候我們需要強制指定函式執行時的 this 是什麼,這時就可以使用 call() 或 apply() 方法。
function run() {
console.log(this);
}
var car = {
maker: 'nissan',
model: 'tiida'
};
// 使用 .call() 將 run 函式內的 this 強制指向 car 物件
run.call(car); // { maker: 'nissan', model: 'tiida' }
4. 硬繫結 (Hard Binding)
bind() 方法會建立一個新的函式,這個新函式的 this 會被永久地繫結到我們所指定的物件上,無論之後如何呼叫它。
function run() {
console.log(this);
}
var car = {
maker: 'nissan',
model: 'tiida'
};
// 建立一個新函式 carRun,它的 this 永遠是 car
var carRun = run.bind(car);
carRun(); // { maker: 'nissan', model: 'tiida' }
5. new 繫結
當函式與 new 關鍵字一起使用時,這個函式就被當作一個建構子 (Constructor)。在這種情況下,this 會指向一個新建立的空物件。
function Product(name) {
// 當使用 new 時,this 會是一個新物件
this.name = name;
// 這個新物件會被隱含地回傳
}
var phone = new Product('Apple iPhone X');
console.log(phone.name); // 'Apple iPhone X'
6. IIFE (立即執行函式)
IIFE (Immediately Invoked Function Expression) 的 this 行為與一般函式相同,遵循全域環境的規則,即使它被定義在一個物件的方法內部。
var obj = {};
var someFunc = function () {
console.log(this === obj); // true,因為 someFunc 是透過 obj 呼叫的
// IIFE
(function () {
console.log("IIFE this:", this); // Window 物件
})();
};
obj.func = someFunc;
obj.func();
如何判斷 this?
總結來說,你可以依照以下順序來判斷 this 的值:
函式是否以
new呼叫?若是,
this就是那個新建立的物件。var bar = new foo();
函式是否以
call或apply呼叫?若是,
this就是明確指定的那個物件。var bar = foo.call(obj);
函式是否作為物件的方法被呼叫 (隱含的繫結)?
若是,
this就是那個物件。var bar = obj.foo();
如果以上皆非
這就是預設情況。在嚴格模式下,
this是undefined;在一般模式下,this是全域物件 (window)。